package net.slashie.libjcsi.textcomponents;

import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import net.slashie.libjcsi.CSIColor;
import net.slashie.libjcsi.CharKey;
import net.slashie.libjcsi.ConsoleSystemInterface;
import net.slashie.util.Util;

/**
 * Box used for various menus.
 * @author Santiago Zapata
 */
public class MenuBox<T> extends TextComponent {
	private static final long serialVersionUID = -2509755918348838909L;
	public static final boolean MultiSelect = true;
	
	private List<T> items;
    private int promptSize;
    private String title = "";

    /* State Attributes */
    private int cursor;
    private int currentPage;
    private int pages;

    /* Components */
    private TextBox promptBox;
    private List<Integer> selection;

    /**
     * Creates a new menu.
     * @param si ConsoleSystemInterface menu is attached to.
     */
    public MenuBox(ConsoleSystemInterface csi) {
        this(csi, false);
    }

    public MenuBox(ConsoleSystemInterface csi, boolean isMultiSelect) {
    	super(csi);
    	promptBox = new TextBox(si);
    	selection = new LinkedList<Integer>();
	}

	/**
     * Allows the position of the menu's upper left corner to be specified.
     * @param x horizontal position
     * @param y vertical position
     */
    @Override
    public void setPosition(int x, int y) {
        super.setPosition(x, y);
        promptBox.setPosition(inPosition.x, inPosition.y + 1);
    }

    /**
     * Sets the height of the menu.
     * @param size menu's height
     */
    public void setPromptSize(int size) {
        promptSize = size;
        promptBox.setHeight(size);
    }

    /**
     * Sets the width of the menu and resets the upper left position to reflect
     * the change of width.
     * @param width menu's width
     */
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        promptBox.setWidth(inWidth);
        promptBox.setPosition(inPosition.x, inPosition.y);
    }

    /**
     * Sets whether or not there is a border on the menu.  The border draws inside
     * the menu's dimensions.
     * @param val true for a border
     */
    @Override
    public void setBorder(boolean val) {
        super.setBorder(val);
        promptBox.setWidth(inWidth);
        promptBox.setPosition(inPosition.x, inPosition.y);
    }

    /**
     * Allows the prompt to be specified.
     * @param prompt String to use as prompt.
     */
    public void setPrompt(String prompt) {
        promptBox.clear();
        promptBox.setText(prompt);
    }

    /**
     * Allows the menu items to be set.
     * @param items Vector of the items in the menu.
     */
    public void setMenuItems(List<T> items) {
        this.items = items;
    }

    /**
     * Prints the menu to the screen.
     */
    @Override
	public void draw() {
        pages = (int) (Math.floor((items.size() - 1) / (inHeight - promptSize)) + 1);
        clearBox();
        drawBorder();
        if (hasBorder()) {
            si.print(position.x + 2, position.y, title);
        }
        promptBox.draw();

        int pageElements = inHeight - promptSize;
        Vector<MenuItem> vector = new Vector<MenuItem>();
        int i = 1;
        for (T item : items) {
			vector.add(new MenuItem(i, item));
			i++;
		}
        List<MenuItem> shownItems = Util.page(vector, pageElements, currentPage);

        i = 0;
        for (; i < shownItems.size(); i++) {
            MenuItem item = shownItems.get(i);
            String description = item.item.toString();
            if (description.length() > getWidth() - 5) {
                description = description.substring(0, getWidth() - 6);
            }
            if (item.index - 1 == cursor) {
            	si.print(inPosition.x + 1, inPosition.y + i + promptSize, "--> " + description + " <--");
            } else {
            	CSIColor color = selection.contains(item.index - 1)? CSIColor.WHITE: CSIColor.GRAY;
            	si.print(inPosition.x + 1, inPosition.y + i + promptSize, item.index + ") " + description, color);
            }
        }
        si.refresh();
    }
    
    public T getItem(int index) {
    	 Vector<T> vector = new Vector<T>();
    	 vector.addAll(items);
         return vector.get(index - 1);
	}

	public void moveDown() {
		cursor++;
		
		if (cursor >= items.size()) {
			cursor = items.size() - 1;
		}
		draw();
	}

	public void moveUp() {
		cursor--;
		
		if (cursor < 0) {
			cursor = 0;
		}
		draw();
	}

	public void selectCursor() {
		if (selection.contains(cursor)) {
			selection.remove(selection.indexOf(cursor));
		} else { 
			selection.add(cursor);
		}
		draw();
	}

	public List<T> getSelected() {
		List<T> result = new LinkedList<T>();
		for (Integer each : selection) {
			result.add(items.get(each));
		}
		return result;
	}


	public T getItemAtCursor() {
		 Vector<T> vector = new Vector<T>();
    	 vector.addAll(items);
         return vector.get(cursor);
	}

    /**
     * 
     * @param s String that will be the title
     */
    public void setTitle(String s) {
        title = s;
    }

	public T getSelection(CharKey key) {
		int pageElements = inHeight - promptSize;
		Vector<T> vector = new Vector<T>();
        vector.addAll(items);
        List<T> shownItems = Util.page(vector, pageElements, currentPage);
        if (key.code == CharKey.SPACE || key.code == CharKey.ESC) {
            return null;
        }
        if (key.code == CharKey.UARROW || key.code == CharKey.N8) {
            if (currentPage > 0) {
                currentPage--;
            }
            return null;
        }
        if (key.code == CharKey.DARROW || key.code == CharKey.N2) {
            if (currentPage < pages - 1) {
                currentPage++;
            }
            return null;
        }
        if (key.code >= CharKey.A && key.code <= CharKey.A + shownItems.size() - 1) {
            return shownItems.get(key.code - CharKey.A);
        } else if (key.code >= CharKey.a && key.code <= CharKey.a + shownItems.size() - 1) {
            return shownItems.get(key.code - CharKey.a);
        }
        return null;
	}
	
	public class MenuItem {
		private final int index;
		private final T item;

		public MenuItem(int index, T item) {
			this.index = index;
			this.item = item;
		}
	}
}